import java.nio.ByteBuffer;

public class ByteBufferIntIntHashMap {

	private int keySetSize;

	private int hashMapCount;

	private EntryMap entryMap;

	public ByteBufferIntIntHashMap(ByteBuffer buf) {
		entryMap = new EntryMap(buf);
		hashMapCount = entryMap.getHashMapCount();
		keySetSize = 0;
	}

	/**
	 * Returns a value associated with the specified key or null, if key is
	 * absent.
	 * 
	 * @param key
	 *            key
	 * @return associated value
	 */
	public Integer get(int key) {
		int hash = Math.abs(key) % hashMapCount;
		EntryMap.Entry e = entryMap.readHeadEntry(hash);

		while (!e.isNull()) {
			if (e.key == key) {
				return e.value;
			} else {
				e = e.next();
			}
		}

		return null;
	}

	/**
	 * Associates key with value within map. If key is already present in the
	 * map, old value is overwritten.
	 * 
	 * @param key
	 *            key
	 * @param value
	 *            new value
	 * 
	 * @return old associated value or null, if key was not in the map
	 * @throws OutOfBufferSpaceException
	 */
	public Integer put(int key, int value) throws OutOfBufferSpaceException {
		int hash = Math.abs(key) % hashMapCount;

		EntryMap.Entry prevEntry = null;
		EntryMap.Entry e = entryMap.readHeadEntry(hash);

		while (!e.isNull() && e.key != key) {
			prevEntry = e;
			e = e.next();
		}

		if (!e.isNull()) {
			int oldValue = e.value;
			e.value = value;
			e.write();
			return oldValue;
		} else {
			e = entryMap.allocateEntry();
			e.key = key;
			e.value = value;
			e.write();

			if (prevEntry != null) {
				prevEntry.next = e.index;
				prevEntry.write();
			} else {
				entryMap.writeHeadEntry(hash, e);
			}

			keySetSize++;

			return null;
		}
	}

	/**
	 * Tells, whether specified key is present in the map.
	 * 
	 * @param key
	 *            key
	 * @return true, if key is present or false otherwise
	 */
	public boolean containsKey(int key) {
		return get(key) != null;
	}

	/**
	 * Removes association from the map.
	 * 
	 * @param key
	 *            key
	 * @return associated value or null, if there was none
	 */
	public Integer remove(int key) {
		int hash = Math.abs(key) % hashMapCount;

		EntryMap.Entry prevEntry = null;
		EntryMap.Entry e = entryMap.readHeadEntry(hash);

		while (!e.isNull() && e.key != key) {
			prevEntry = e;
			e = e.next();
		}

		if (!e.isNull()) {
			if (prevEntry == null) {
				entryMap.writeHeadEntry(hash, e.next());
			} else {
				prevEntry.next = e.next;
				prevEntry.write();
			}
			entryMap.disposeEntry(e);
			keySetSize--;
			return e.value;
		} else {
			return null;
		}
	}

	/**
	 * Clears the map.
	 */
	public void clear() {
		entryMap.reset();
		keySetSize = 0;
	}

	/**
	 * Returns key set size.
	 * 
	 * @return key set size
	 */
	public int keySetSize() {
		return keySetSize;
	}

	/**
	 * Returns hash map capacity (how many associations it may contain, before
	 * buffer becomes full).
	 * 
	 * @return hash map capacity
	 */
	public int getCapacity() {
		return entryMap.getEntryMapCount();
	}
}
